/*
 * The MIT License
 * Copyright (c) 2012 kc4.org
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.kc4.sso.crypto;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.UUID;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.kc4.util.Hex;

/**
 * による、対称鍵暗号器。
 *
 * @author yoichi kikuchi
 */
public class SSOSymmetricCipher implements Serializable {

	private static final long serialVersionUID = 1L;

	private static final String CLEARTEXT_CHARSET = "UTF-8";

	/** 対照鍵暗号化アルゴリズム "Blowfish" */
	public static final String BLOWFISH = "Blowfish";

	/** 対照鍵暗号化アルゴリズム "AES" */
	public static final String AES = "AES";

	private static final String HASH_ALGORITHM = "MD5";

	private String commonkeyAlgorithm = AES;

	private SecretKey commonkey;

	/**
	 * 対象鍵暗号器を生成します。
	 *
	 * @param commonkey 16進数文字列形式の共通鍵。
	 */
	public SSOSymmetricCipher(String commonkey) {

		this.setCommonkey(commonkey);
	}

	/**
	 * 対象鍵暗号器を生成します。
	 *
	 * @param commonkey バイト配列形式の共通鍵。
	 */
	public SSOSymmetricCipher(byte[] commonkey) {

		this.setCommonkey(commonkey);
	}

	/**
	 * 対称鍵暗号器を生成します。
	 * インスタンス生成時に共通鍵が生成されます。
	 */
	public SSOSymmetricCipher() {

		this.setCommonkey(this.newCommonkey());
	}

	/**
	 * 平文を暗号化します。
	 *
	 * @param cleartext 16進数文字列形式の平文。
	 * @return 16進数文字列形式の暗号文。
	 */
	public String encryptAsHex(String cleartext) {

		return new Hex(this.encrypt(cleartext)).toString();
	}

	/**
	 * 平文を暗号化します。
	 *
	 * @param cleartext 平文。
	 * @return 16進数文字列形式の暗号文。
	 */
	public String encryptAsHex(byte[] cleartext) {

		return new Hex(this.encrypt(cleartext)).toString();
	}

	/**
	 * 平文を暗号化します。
	 *
	 * @param cleartext 16進数文字列形式の平文。
	 * @return 暗号文。
	 */
	public byte[] encrypt(String cleartext) {

		return this.encrypt(new Hex(cleartext).toBytes());
	}

	/**
	 * 平文を暗号化します。
	 *
	 * @param cleartext 平文。
	 * @return 暗号文。
	 */
	public byte[] encrypt(byte[] cleartext) {

		try {

			Cipher symmetric = Cipher.getInstance(this.getCommonkeyAlgorithm());
			symmetric.init(Cipher.ENCRYPT_MODE, this.getCommonkey());
			return symmetric.doFinal(cleartext);
		}
		catch (Exception e) {

			throw new IllegalArgumentException(e);
		}
	}

	/**
	 * 暗号文を復号します。
	 * 暗号文のもととなった平文の文字セットは、UTF-8 である必要があります。
	 *
	 * @param encrypted 16進数文字列形式の暗号文。
	 * @return 文字列形式の平文。
	 */
	public String decryptAsString(String encrypted) {

		return this.decryptAsString(encrypted, Charset.forName(CLEARTEXT_CHARSET));
	}

	/**
	 * 暗号文を復号します。
	 * 暗号文のもととなった平文の文字セットは、UTF-8 である必要があります。
	 *
	 * @param encrypted 暗号文。
	 * @return 文字列形式の平文。
	 */
	public String decryptAsString(byte[] encrypted) {

		return this.decryptAsString(encrypted, Charset.forName(CLEARTEXT_CHARSET));
	}

	/**
	 * 暗号文を復号します。
	 *
	 * @param encrypted 16進数文字列形式の暗号文。
	 * @param charset 平文の文字セット。
	 * @return 文字列形式の平文。
	 */
	public String decryptAsString(String encrypted, Charset charset) {

		return new String(this.decrypt(encrypted), charset);
	}

	/**
	 * 暗号文を復号します。
	 *
	 * @param encrypted 暗号文。
	 * @param charset 平文の文字セット。
	 * @return 文字列形式の平文。
	 */
	public String decryptAsString(byte[] encrypted, Charset charset) {

		return new String(this.decrypt(encrypted), charset);
	}

	/**
	 * 暗号文を復号します。
	 *
	 * @param encrypted 16進数文字列形式の暗号文。
	 * @return 16進数文字列形式の平文。
	 */
	public String decryptAsHex(String encrypted) {

		return new Hex(this.decrypt(encrypted)).toString();
	}

	/**
	 * 暗号文を復号します。
	 *
	 * @param encrypted 暗号文。
	 * @return 16進数文字列形式の平文。
	 */
	public String decryptAsHex(byte[] encrypted) {

		return new Hex(this.decrypt(encrypted)).toString();
	}

	/**
	 * 暗号文を復号します。
	 *
	 * @param encrypted 16進数文字列形式の暗号文。
	 * @return 平文。
	 */
	public byte[] decrypt(String encrypted) {

		return this.decrypt(new Hex(encrypted).toBytes());
	}

	/**
	 * 暗号文を復号します。
	 *
	 * @param encrypted 暗号文。
	 * @return 平文。
	 */
	public byte[] decrypt(byte[] encrypted) {

		try {

			Cipher symmetric = Cipher.getInstance(this.getCommonkeyAlgorithm());
			symmetric.init(Cipher.DECRYPT_MODE, this.getCommonkey());
			return symmetric.doFinal(encrypted);
		}
		catch (Exception e) {

			throw new IllegalStateException(e);
		}
	}

	/**
	 * 共通鍵暗号アルゴリズムを取得します。
	 *
	 * @return 共通鍵暗号アルゴリズム。
	 */
	protected String getCommonkeyAlgorithm() {

		return this.commonkeyAlgorithm;
	}

	/**
	 * 共通鍵暗号アルゴリズムを設定します。
	 *
	 * @param commonkeyAlgorithm 共通鍵暗号アルゴリズム。
	 */
	protected void setCommonkeyAlgorithm(String commonkeyAlgorithm) {

		this.commonkeyAlgorithm = commonkeyAlgorithm;
	}

	/**
	 * 共通鍵を生成します。
	 *
	 * @return 共通鍵。
	 */
	protected SecretKey newCommonkey() {

		try {

			MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
			md.update(UUID.randomUUID().toString().getBytes());
			SecretKey secretkey = new SecretKeySpec(md.digest(), this.getCommonkeyAlgorithm());

			return secretkey;
		}
		catch (Exception e) {

			throw new IllegalArgumentException(e);
		}
	}

	/**
	 * 16進数文字列形式の共通鍵を取得します。
	 *
	 * @return 16進数文字列形式の共通鍵。
	 */
	public String getCommonkeyAsHex() {

		return new Hex(this.getCommonkeyAsBytes()).toString();
	}

	/**
	 * バイト配列形式の共通鍵を取得します。
	 *
	 * @return バイト配列形式の共通鍵。
	 */
	protected byte[] getCommonkeyAsBytes() {

		return this.getCommonkey().getEncoded();
	}

	/**
	 * 共通鍵を取得します。
	 *
	 * @return 共通鍵。
	 */
	protected SecretKey getCommonkey() {

		return this.commonkey;
	}

	/**
	 * 16進数文字列形式の共通鍵を設定します。
	 *
	 * @param commonkey 16進数文字列形式の共通鍵。
	 */
	protected void setCommonkey(String commonkey) {

		this.setCommonkey(new Hex(commonkey).toBytes());
	}

	/**
	 * バイト配列形式の共通鍵を設定します。
	 *
	 * @param commonkey バイト配列形式の共通鍵。
	 */
	protected void setCommonkey(byte[] commonkey) {

		this.setCommonkey(new SecretKeySpec(commonkey,
			this.getCommonkeyAlgorithm()));
	}

	/**
	 * 共通鍵を設定します。
	 *
	 * @param commonkey 共通鍵。
	 */
	protected void setCommonkey(SecretKey commonkey) {

		this.commonkey = commonkey;
	}


}
